/*
* Copyright (C) 2010-2012 Magnus Skjegstad
* 
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
* 
* The latest version of this library can be downloaded from http://mist-pubsub.googlecode.com
* 
*/

package com.skjegstad.mist2.managers;

import com.skjegstad.simpleevents.SimpleEvent;
import com.skjegstad.simpleevents.SimpleEventListener;
import com.skjegstad.mist2.datatypes.ConnectionIdentifier;
import com.skjegstad.mist2.datatypes.NodeIdentifier;
import com.skjegstad.mist2.events.ExceptionEvent;
import com.skjegstad.mist2.events.NewNodeDiscoveredEvent;
import com.skjegstad.mist2.events.RegisterNodeEvent;
import com.skjegstad.mist2.events.RemoveNodeEvent;
import com.skjegstad.mist2.exceptions.MistException;
import com.skjegstad.mist2.storage.KnownNodesStore;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 *
 * @author Magnus Skjegstad
 */
public class KnownNodesStoreManager {
    private final ConcurrentMap<ConnectionIdentifier, KnownNodesStore> nodesPerConnection =
            new ConcurrentHashMap<ConnectionIdentifier, KnownNodesStore>();

    private final MistEventManager mistEventManager;
    private final NodeIdentifier myNodeId;

    public KnownNodesStoreManager(NodeIdentifier myNodeId, MistEventManager mistEventManager) {
        this.mistEventManager = mistEventManager;
        this.myNodeId = myNodeId;
        registerEvents();
    }

    private void registerEvents() {

        mistEventManager.getNodeManagerEventPipe().listen(new SimpleEventListener() {
            public void receiveEvent(SimpleEvent event) {
                
                // Event to remove existing node
                if (event instanceof RemoveNodeEvent) {
                    RemoveNodeEvent e = (RemoveNodeEvent) event;
                    disconnectNodes(e.getConnectionIdentifier(), e.getNodeIdentifiers());
                }
                
                // Event to register new node
                if (event instanceof RegisterNodeEvent) {
                    RegisterNodeEvent e = (RegisterNodeEvent) event;

                    if (e.getSourceNodeId().equals(myNodeId)) // no point in adding our own node
                        return;

                    // register as known node
                    KnownNodesStore k = getKnownNodesStore(e.getConnectionIdentifier());
                    if (k == null) {
                        mistEventManager.throwException(new MistException("No KnownNodesStore registered for connection id " + e.getConnectionIdentifier()));
                        return;
                    }

                    // if k.put == null, it is the first time the node is registered on the connection - this triggers the NewNodeDiscoveredEvent
                    if (k.put(e.getSourceNodeId(), e.getTopicsWithTtl(), e.getAcknowledgment()) == null)
                        mistEventManager.getNodeManagerEventPipe().trigger(
                                new NewNodeDiscoveredEvent(e.getConnectionIdentifier(), e.getSourceNodeId(), e.getIpAddress()));
                }
            }
        });
        

    }

    public KnownNodesStore createKnownNodesStore(ConnectionIdentifier connectionIdentifier) {
        KnownNodesStore kns = new KnownNodesStore();

        nodesPerConnection.put(connectionIdentifier, kns);

        return kns;
    }

    public KnownNodesStore getKnownNodesStore(ConnectionIdentifier connectionIdentifier) {
        return nodesPerConnection.get(connectionIdentifier);
    }

    public Collection<KnownNodesStore> getKnownNodesForAllConnections() {
        return nodesPerConnection.values();
    }

    protected boolean disconnectNodes(ConnectionIdentifier connection, Collection<NodeIdentifier> nodes) {
            // remove from known nodes
            KnownNodesStore k = nodesPerConnection.get(connection);
            if (k != null) {
                for (NodeIdentifier n : nodes) {
                    k.removeNode(n);
/*                    if (k.getNodes().isEmpty()) {
                        nodesPerConnection.remove(connection);
                        break;
                    }*/
                }
                return true;
            }

            return false;
    }
}
